package com.spring.dynamicdatasource.controller;

import com.spring.dynamicdatasource.dto.ConnectionDTO;
import com.spring.dynamicdatasource.service.DataSourceService;
import com.spring.dynamicdatasource.utils.ExceptionNoneParamsFunction;
import com.spring.dynamicdatasource.utils.JdbcUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import javax.validation.Valid;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author HouKunLin
 * @date 19-9-9 下午11:06
 */
@RestController
@RequestMapping("/")
public class HomeController {

    private final DataSource dataSource;

    private final HttpServletRequest request;

    private final DataSourceService dataSourceService;

    public HomeController(DataSource dataSource, HttpServletRequest request, DataSourceService dataSourceService) {
        this.dataSource = dataSource;
        this.request = request;
        this.dataSourceService = dataSourceService;
    }

    /**
     * 注册一个数据源
     *
     * @param dto
     * @param errors
     * @return
     */
    @RequestMapping("/register")
    public Object register(@Valid ConnectionDTO dto, Errors errors) {
        if (errors.hasErrors()) {
            return errors.getAllErrors();
        }
        System.out.println(request.getRequestURI());
        System.out.println("当前会话ID:" + request.getSession().getId());
        try {
            DataSource dataSource = dataSourceService.registerDateSource(dto);

            return "OK:" + dataSource;
        } catch (Exception e) {
            e.printStackTrace();
            return "error:" + e.getMessage();
        }
    }

    @RequestMapping("/test/base")
    public Object test1(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Map<String, Object> databaseMap = new LinkedHashMap<>();
        Map<String, Object> driverMap = new LinkedHashMap<>();
        Map<String, Object> termMap = new LinkedHashMap<>();
        Map<String, Object> functionsMap = new LinkedHashMap<>();
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();
        map.put("getURL", metaData.getURL());
        map.put("getUserName", metaData.getUserName());
        map.put("getExtraNameCharacters", metaData.getExtraNameCharacters());
        map.put("getCatalogSeparator", metaData.getCatalogSeparator());

        termMap.put("getCatalogTerm", metaData.getCatalogTerm());
        termMap.put("getProcedureTerm", metaData.getProcedureTerm());
        termMap.put("getSchemaTerm", metaData.getSchemaTerm());

        termMap.put("getJDBCMajorVersion", metaData.getJDBCMajorVersion());
        termMap.put("getJDBCMinorVersion", metaData.getJDBCMinorVersion());

        functionsMap.put("getNumericFunctions", metaData.getNumericFunctions());
        functionsMap.put("getStringFunctions", metaData.getStringFunctions());
        functionsMap.put("getSystemFunctions", metaData.getSystemFunctions());
        functionsMap.put("getTimeDateFunctions", metaData.getTimeDateFunctions());

        driverMap.put("getDriverName", metaData.getDriverName());
        driverMap.put("getDriverVersion", metaData.getDriverVersion());
        driverMap.put("getDriverMajorVersion", metaData.getDriverMajorVersion());
        driverMap.put("getDriverMinorVersion", metaData.getDriverMinorVersion());

        databaseMap.put("getDatabaseProductName", metaData.getDatabaseProductName());
        databaseMap.put("getDatabaseProductVersion", metaData.getDatabaseProductVersion());
        databaseMap.put("getDatabaseMajorVersion", metaData.getDatabaseMajorVersion());
        databaseMap.put("getDatabaseMinorVersion", metaData.getDatabaseMinorVersion());

        map.put("driverInfo", driverMap);
        map.put("databaseInfo", databaseMap);
        map.put("termInfo", termMap);
        map.put("functionsInfo", functionsMap);

        tryMethod(() -> {
            map.put("Attributes", JdbcUtils.resultSetToMaps(metaData.getAttributes(catalog, schema, null, null)));
        });
        tryMethod(() -> {
            map.put("ClientInfoProperties", JdbcUtils.resultSetToMaps(metaData.getClientInfoProperties()));
        });
        tryMethod(() -> {
            map.put("UDTs", JdbcUtils.resultSetToMaps(metaData.getUDTs(catalog, schema, null, null)));
        });
        return map;
    }

    @RequestMapping("/test/schemas")
    public Object test2(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();

        tryMethod(() -> {
            map.put("Schemas1", JdbcUtils.resultSetToMaps(metaData.getSchemas()));
            map.put("Schemas2", JdbcUtils.resultSetToMaps(metaData.getSchemas(catalog, null)));
        });
        tryMethod(() -> {
            map.put("Catalogs", JdbcUtils.resultSetToMaps(metaData.getCatalogs()));
        });

        return map;
    }


    @RequestMapping("/test/tables")
    public Object test3(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();

        tryMethod(() -> {
            map.put("SuperTables", JdbcUtils.resultSetToMaps(metaData.getSuperTables(catalog, schema, tableName)));
        });
        tryMethod(() -> {
            map.put("SuperTypes", JdbcUtils.resultSetToMaps(metaData.getSuperTypes(catalog, schema, tableName)));
        });
        tryMethod(() -> {
            map.put("TablePrivileges", JdbcUtils.resultSetToMaps(metaData.getTablePrivileges(catalog, schema, tableName)));
        });
        tryMethod(() -> {
            map.put("Tables", JdbcUtils.resultSetToMaps(metaData.getTables(catalog, schema, tableName, null)));
        });
        tryMethod(() -> {
            map.put("TableTypes", JdbcUtils.resultSetToMaps(metaData.getTableTypes()));
        });
        tryMethod(() -> {
            map.put("TypeInfo", JdbcUtils.resultSetToMaps(metaData.getTypeInfo()));
        });
        return map;
    }


    @RequestMapping("/test/functions")
    public Object test4(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();

        tryMethod(() -> {
            map.put("FunctionColumns", JdbcUtils.resultSetToMaps(metaData.getFunctionColumns(catalog, schema, null, null)));
        });
        tryMethod(() -> {
            map.put("Functions", JdbcUtils.resultSetToMaps(metaData.getFunctions(catalog, schema, null)));
        });
        return map;
    }


    @RequestMapping("/test/keys")
    public Object test5(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();

        tryMethod(() -> {
            map.put("BestRowIdentifier", JdbcUtils.resultSetToMaps(metaData.getBestRowIdentifier(catalog, schema, tableName, 0, true)));
        });
        tryMethod(() -> {
            map.put("ExportedKeys", JdbcUtils.resultSetToMaps(metaData.getExportedKeys(catalog, schema, tableName)));
        });
        tryMethod(() -> {
            map.put("ImportedKeys", JdbcUtils.resultSetToMaps(metaData.getImportedKeys(catalog, schema, tableName)));
        });
        tryMethod(() -> {
            map.put("IndexInfo", JdbcUtils.resultSetToMaps(metaData.getIndexInfo(catalog, schema, tableName, false, true)));
        });
        tryMethod(() -> {
            map.put("PrimaryKeys", JdbcUtils.resultSetToMaps(metaData.getPrimaryKeys(catalog, schema, tableName)));
        });
        return map;
    }

    @RequestMapping("/test/procedures")
    public Object test6(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();

        tryMethod(() -> {
            map.put("ProcedureColumns", JdbcUtils.resultSetToMaps(metaData.getProcedureColumns(catalog, schema, null, null)));
        });
        tryMethod(() -> {
            map.put("Procedures", JdbcUtils.resultSetToMaps(metaData.getProcedures(catalog, schema, null)));
        });
        return map;
    }

    @RequestMapping("/test/columns")
    public Object test7(@RequestParam(name = "catalog", defaultValue = "%") String catalog,
                        @RequestParam(name = "schema", defaultValue = "%") String schema,
                        @RequestParam(name = "tableName", defaultValue = "%") String tableName) throws SQLException {
        Map<String, Object> map = new LinkedHashMap<>();
        url(map, catalog, schema, tableName);
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();

        tryMethod(() -> {
            map.put("ColumnPrivileges", JdbcUtils.resultSetToMaps(metaData.getColumnPrivileges(catalog, schema, tableName, null)));
        });
        tryMethod(() -> {
            map.put("Columns", JdbcUtils.resultSetToMaps(metaData.getColumns(catalog, schema, tableName, null)));
        });
        tryMethod(() -> {
            map.put("CrossReference", JdbcUtils.resultSetToMaps(metaData.getCrossReference(catalog, schema, tableName, null, null, null)));
        });
        tryMethod(() -> {
            map.put("PseudoColumns", JdbcUtils.resultSetToMaps(metaData.getPseudoColumns(catalog, schema, tableName, null)));
        });
        tryMethod(() -> {
            map.put("VersionColumns", JdbcUtils.resultSetToMaps(metaData.getVersionColumns(catalog, schema, tableName)));
        });
        return map;
    }

    private void url(Map<String, Object> map, String catalog, String schema, String tableName) {
        String params;
        try {
            params = String.format("?catalog=%s&schema=%s&tableName=%s",
                    URLEncoder.encode(catalog, "utf-8"),
                    URLEncoder.encode(schema, "utf-8"),
                    URLEncoder.encode(tableName, "utf-8")
            );
        } catch (Exception e) {
            params = "?err=" + e.getMessage();
            e.printStackTrace();
        }
        String urlFormatString = String.format("http://%s:%s/", request.getLocalAddr(), request.getServerPort());
        String[] uris = new String[]{"/test/base", "/test/schemas", "/test/tables", "/test/functions", "/test/keys", "/test/procedures", "/test/columns"};
        for (String uri : uris) {
            mapPutTestUrl(map, urlFormatString, uri, params);
        }
    }

    private void mapPutTestUrl(Map<String, Object> map, String baseUrl, String uri, String params) {
        map.put(uri, String.format("%s%s%s", baseUrl, uri, params));
    }

    private void tryMethod(ExceptionNoneParamsFunction function) {
        try {
            function.apply();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
